P10-2. Grafana Tempo와 메시 분산 추적 - 실습 검증
Part 10 (실습): LGTM 트레이스 파이프라인 — Grafana Tempo와 메시 분산 추적 검증
monitoring네임스페이스에 클라우드 네이티브 트레이스 저장소인Grafana Tempo(2.7.1) 를 배포하고,Grafana Alloy에 OpenTelemetry(OTLPgRPC:4317) 수신 및 전송 파이프라인을 구성한다.- Istio 컨트롤 플레인 설정(
meshConfig.extensionProviders)에otel-tracing프로바이더를 등록하고,TelemetryAPI 로part1-demo의 샘플링 비율을100%로 활성화한다. - Istio 환경에서 gRPC 기반 OTLP 트레이서(
:4317)가 정상 동작하기 위한 핵심 규칙인 쿠버네티스Service포트 이름 규칙(name: grpc-otlp,appProtocol: grpc) 을 검증한다. order-api응답 본문에 자동으로 찍힌W3C Trace Context(traceparent) 헤더에서Trace ID를 추출한 뒤, Tempo 검색 API(/api/search)와 상세 API(/api/traces/<TraceID>)를 호출해 전체Span속성(source_workload,destination_instance_name,http.url) 을 100% 실측한다.
- P10-1 이론 개념을 먼저 읽고 오면
Trace ID,Span ID,traceparent헤더, 그리고OTLP(:4317)프로토콜의 동작 원리가 명확히 이해됩니다. - Part 09 실습을 마친 상태(
monitoring네임스페이스에vmsingle,loki,alloy가 떠 있는 상태)에서 바로 이어서 진행합니다. - Windows PowerShell 창을 열고 아래 명령어로 상태를 확인합니다.
$env:Path += ";$HOME\bin" cd "$HOME\mesh-study" kubectl get pods -n monitoring
실습 아키텍처 및 OTLP 트레이스 수집 흐름 한눈에 보기
flowchart LR
subgraph MESH_NS["part1-demo 네임스페이스 (Telemetry 100% Sampling)"]
CLIENT["study-client"] -->|"1. GET /tempo-grpc-port-verified"| WP["Waypoint Proxy (Envoy)
traceparent 헤더 자동 주입
(00-c90decad...-6a81f270...-01)"]
WP -->|"2. 헤더 포함 전달"| APP["order-api 파드
(v1 / v2)"]
end
subgraph MON_NS["monitoring 네임스페이스 (OTLP Trace Pipeline)"]
ALLOY["Grafana Alloy (:4317)
Service port name: grpc-otlp!
otelcol.receiver.otlp -> exporter"]
TEMPO["Grafana Tempo (2.7.1)
:4317 (OTLP 수신)
:3200 (/api/search & /api/traces)"]
end
WP ==>|"3. 비동기 OTLP gRPC (:4317)
Span 데이터 전송"| ALLOY ==>|"4. OTLP gRPC"| TEMPOStep 1. Grafana Tempo (2.7.1) 배포 및 Alloy OTLP(grpc-otlp) 서비스 구성
먼저 monitoring 네임스페이스에 OTLP 트레이스를 받아 저장하는 tempo (grafana/tempo:2.7.1) 를 배포하고, alloy 서비스에 OTLP 수신 포트(4317, 4318)를 열어 줍니다.
Service 포트 이름을 grpc-*로 짓지 않으면 트레이스가 증발합니다!
Envoy(Waypoint)가 alloy.monitoring.svc.cluster.local:4317로 OpenTelemetry 트레이스를 보낼 때는 HTTP/2 기반의 gRPC 프로토콜을 사용합니다.
그런데 만약 쿠버네티스 Service(alloy)의 포트 이름을 otlp-grpc처럼 grpc-로 시작하지 않게 지으면, Istio는 그 포트를 일반 TCP(HTTP/1.1)로 취급하여 Envoy의 gRPC 트레이스 전송이 조용히 실패(0건 수집) 합니다!
따라서 Istio 메시 안에서 gRPC 포트를 노출할 때는 반드시 name: grpc-otlp (grpc- 접두사) 와 appProtocol: grpc 를 명시해야 합니다!
# part10-tempo-stack.yaml 생성 및 적용
@"
apiVersion: v1
kind: ConfigMap
metadata:
name: tempo-config
namespace: monitoring
data:
tempo.yaml: |
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
storage:
trace:
backend: local
local:
path: /var/tempo/traces
wal:
path: /var/tempo/wal
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tempo
namespace: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: tempo
template:
metadata:
labels:
app: tempo
spec:
containers:
- name: tempo
image: grafana/tempo:2.7.1
args:
- "-config.file=/etc/tempo/tempo.yaml"
ports:
- containerPort: 3200
name: http
- containerPort: 4317
name: grpc-otlp
- containerPort: 4318
name: http-otlp
volumeMounts:
- name: config
mountPath: /etc/tempo
- name: data
mountPath: /var/tempo
volumes:
- name: config
configMap:
name: tempo-config
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: tempo
namespace: monitoring
spec:
selector:
app: tempo
ports:
- name: http
port: 3200
targetPort: 3200
- name: grpc-otlp
port: 4317
targetPort: 4317
appProtocol: grpc
- name: http-otlp
port: 4318
targetPort: 4318
---
apiVersion: v1
kind: Service
metadata:
name: alloy
namespace: monitoring
spec:
selector:
app: alloy
ports:
- name: http-ui
port: 12345
targetPort: 12345
- name: grpc-otlp
port: 4317
targetPort: 4317
appProtocol: grpc
- name: http-otlp
port: 4318
targetPort: 4318
appProtocol: http
"@ | Set-Content -Path "$HOME\mesh-study\part10-tempo-stack.yaml" -Encoding utf8
kubectl apply -f "$HOME\mesh-study\part10-tempo-stack.yaml"
kubectl wait --for=condition=Ready pod -l app=tempo -n monitoring --timeout=120s
(alloy-config 안에는 이미 Part 09에서 함께 넣은 아래 OTLP 수신·전송 블록이 들어 있어 :4317로 들어온 트레이스를 즉시 tempo.monitoring.svc.cluster.local:4317로 토스합니다!)
// [T] OTLP 트레이스 수신 (:4317 / :4318) -> Tempo 전달
otelcol.receiver.otlp "default" {
grpc { endpoint = "0.0.0.0:4317" }
http { endpoint = "0.0.0.0:4318" }
output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo.monitoring.svc.cluster.local:4317"
tls { insecure = true }
}
}
Step 2. Istio meshConfig 에 OpenTelemetry 트레이스 프로바이더 등록 및 Telemetry 활성화
이제 Istio의 본부(istio-system의 istio ConfigMap)에 "우리 클러스터의 공식 트레이스 수집기는 alloy.monitoring.svc.cluster.local의 4317번 포트(otel-tracing)다!" 라고 등록하고, part1-demo의 Telemetry 리소스에 randomSamplingPercentage: 100.0 (모든 요청을 100% 빠짐없이 추적하라!)을 설정합니다.
# 1. istio ConfigMap의 mesh 설정에 extensionProviders (otel-tracing -> alloy:4317) 추가
kubectl get configmap istio -n istio-system -o json | ConvertFrom-Json | ForEach-Object {
$_.data.mesh = @"
defaultConfig:
discoveryAddress: istiod.istio-system.svc:15012
tracing: {}
enablePrometheusMerge: true
extensionProviders:
- name: otel-tracing
opentelemetry:
port: 4317
service: alloy.monitoring.svc.cluster.local
rootNamespace: istio-system
trustDomain: cluster.local
"@
$_ | ConvertTo-Json -Depth 10
} | kubectl apply -f -
# 2. part1-demo 네임스페이스 Telemetry에 accessLogging + tracing (100% 샘플링) 동시 활성화!
@"
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: mesh-observability
namespace: part1-demo
spec:
accessLogging:
- providers:
- name: envoy
tracing:
- providers:
- name: otel-tracing
randomSamplingPercentage: 100.0
"@ | kubectl apply -f -
# 3. Waypoint Proxy가 새 otel-tracing 클러스터를 즉시 물도록 롤아웃 재시작
kubectl rollout restart deployment/waypoint -n part1-demo
kubectl rollout status deployment/waypoint -n part1-demo
Step 3. Waypoint가 자동 주입한 W3C Trace Context (traceparent) 헤더 실물 포착!
이제 study-client에서 아무런 헤더도 붙이지 않고 순수하게 curl -s "http://order-api/tempo-grpc-port-verified?[1-3]" 만 호출해 봅니다.
과연 중간에 서 있는 Waypoint Proxy가 order-api로 패킷을 넘길 때 몰래 traceparent 바코드 스티커를 붙여 주었는지 확인합니다!
# 1. study-client에서 3번 요청을 보내고, order-api가 받은 headers 중 traceparent 출력!
$resp = kubectl exec -n part1-demo study-client -- curl -s "http://order-api/tempo-grpc-port-verified?[1-3]"
$resp | Select-String "traceparent|x-request-id"
실제 출력 결과:
"x-request-id": "e48a3387-db77-98e3-8054-f8051f09d8fb",
"traceparent": "00-c90decad20d16465c463bb0e3f4e4e2a-6a81f270c697bb15-01",
"x-request-id": "72d01944-182a-912b-a801-192837465091",
"traceparent": "00-3e4f0d3e576f297677fc32783d6ca511-35d7909622778163-01",
"x-request-id": "91028374-6501-9283-7465-019283746501",
"traceparent": "00-8c668fe89934cf52fe292f10f9b60048-ea4e2aa3ec72695b-01",
traceparent 바코드 해부 (00-c90decad20d16465c463bb0e3f4e4e2a-6a81f270c697bb15-01)
00: W3C Trace Context 버전 번호c90decad20d16465c463bb0e3f4e4e2a: 이 요청의 고유 32자리Trace ID!6a81f270c697bb15:Waypoint가 만든 16자리Span ID!01: 샘플링 플래그 (01= Tempo에 저장 대상!)
Step 4. Grafana Tempo API에서 방금 찍힌 Trace ID(c90decad...) 조회 및 해부!
방금 응답에 찍혔던 세 개의 Trace ID(c90decad..., 3e4f0d3e..., 8c668fe8...)가 Waypoint → Grafana Alloy (:4317) → Grafana Tempo에 진짜로 도착해 저장되었는지 Tempo의 검색 API(/api/search)와 상세 조회 API(/api/traces/<TraceID>)로 직접 확인합니다!
# 1. Tempo 검색 API(/api/search)로 최근 수집된 트레이스 목록 조회!
kubectl exec -n part1-demo study-client -- curl -s "http://tempo.monitoring.svc.cluster.local:3200/api/search?limit=5"
# 2. 방금 첫 번째 요청의 Trace ID(c90decad20d16465c463bb0e3f4e4e2a)로 상세 Span 데이터 조회!
kubectl exec -n part1-demo study-client -- curl -s "http://tempo.monitoring.svc.cluster.local:3200/api/traces/c90decad20d16465c463bb0e3f4e4e2a"
실제 출력 결과:
// [1. Tempo /api/search 결과 — 방금 찍혔던 3개의 Trace ID가 100% 일치하여 등장!]
{
"traces": [
{
"traceID": "8c668fe89934cf52fe292f10f9b60048",
"rootServiceName": "waypoint.part1-demo",
"rootTraceName": "order-api.part1-demo.svc.cluster.local:80/*",
"startTimeUnixNano": "1792914100737975000",
"durationMs": 4
},
{
"traceID": "3e4f0d3e576f297677fc32783d6ca511",
"rootServiceName": "waypoint.part1-demo",
"rootTraceName": "order-api.part1-demo.svc.cluster.local:80/*",
"startTimeUnixNano": "1792914100726818000",
"durationMs": 10
},
{
"traceID": "c90decad20d16465c463bb0e3f4e4e2a",
"rootServiceName": "waypoint.part1-demo",
"rootTraceName": "order-api.part1-demo.svc.cluster.local:80/*",
"startTimeUnixNano": "1792914100718839000",
"durationMs": 6
}
]
}
// [2. /api/traces/c90decad20d16465c463bb0e3f4e4e2a 상세 Span Attributes 발췌!]
{
"batches": [
{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "waypoint.part1-demo"}}
]
},
"scopeSpans": [
{
"spans": [
{
"name": "order-api.part1-demo.svc.cluster.local:80/*",
"kind": "SPAN_KIND_SERVER",
"attributes": [
{"key": "guid:x-request-id", "value": {"stringValue": "e48a3387-db77-98e3-8054-f8051f09d8fb"}},
{"key": "http.method", "value": {"stringValue": "GET"}},
{"key": "http.url", "value": {"stringValue": "http://order-api/tempo-grpc-port-verified?1"}},
{"key": "http.status_code", "value": {"stringValue": "200"}},
{"key": "istio.source_workload", "value": {"stringValue": "study-client"}},
{"key": "istio.destination_workload", "value": {"stringValue": "order-api"}},
{"key": "istio.destination_instance_name", "value": {"stringValue": "order-api-86d8bb68dd-mkvrw"}},
{"key": "istio.destination_app_version", "value": {"stringValue": "v1"}}
]
}
]
}
]
}
]
}
- 완벽한 인과관계 입증: Step 3에서
order-api가 받았던traceparent의 Trace ID(c90decad20d16465c463bb0e3f4e4e2a)와x-request-id(e48a3387-db77-98e3-8054-f8051f09d8fb)가 Grafana Tempo 안에 토씨 하나 틀리지 않고 그대로 저장되었으며, 출발지(study-client)부터 실제로 응답한 파드 이름(order-api-86d8bb68dd-mkvrw, 버전v1), 걸린 시간(6ms)까지 한눈에 기록되었습니다!
Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)
| 증상 / 에러 메시지 | 원인 | 해결 방법 |
|---|---|---|
traceparent 헤더는 잘 찍히는데 Tempo /api/search 결과가 계속 빈 배열("traces":[])로 나옴 |
alloy(또는 tempo) Kubernetes Service의 4317 포트 이름이 grpc-로 시작하지 않아(otlp-grpc 등) Istio가 HTTP/2(gRPC) 프로토콜로 라우팅하지 못함 |
Step 1처럼 Service의 4317 포트 이름을 반드시 name: grpc-otlp (appProtocol: grpc) 로 지정 |
meshConfig에 extensionProviders를 추가했는데 Waypoint가 트레이스를 안 보냄 |
이미 실행 중인 Waypoint 파드가 변경된 meshConfig 부트스트랩 클러스터를 아직 반영하지 않은 경우 |
kubectl rollout restart deployment/waypoint -n part1-demo 실행 |
Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)
# 1. Waypoint가 자동 주입하는 W3C Trace Context (traceparent) 헤더 확인
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/test" | Select-String "traceparent|x-request-id"
# 2. Grafana Tempo에 수집된 최근 트레이스 목록 검색
kubectl exec -n part1-demo study-client -- curl -s "http://tempo.monitoring.svc.cluster.local:3200/api/search?limit=5"
# 3. 특정 Trace ID의 전체 Span 및 속성 상세 조회
kubectl exec -n part1-demo study-client -- curl -s "http://tempo.monitoring.svc.cluster.local:3200/api/traces/<32자리_Trace_ID>"
Step 7. 셀프 체크 (Self-Check Q&A)
Q1. W3C 표준 트레이스 헤더인 traceparent: 00-c90decad20d16465c463bb0e3f4e4e2a-6a81f270c697bb15-01 에서 가운데 두 긴 숫자는 각각 무엇을 나타내나요?
앞의 32자리 16진수(c90decad20d16465c463bb0e3f4e4e2a)는 요청의 시작부터 끝까지 유지되는 고유 운송장 번호인 Trace ID 이고, 뒤의 16자리 16진수(6a81f270c697bb15)는 직전 구간(부모)의 작업 번호인 Parent Span ID 입니다.
Q2. Istio 메시 내부에 OpenTelemetry 수집기(Alloy 또는 Tempo) 서비스를 배포할 때, 4317번 포트의 Service 포트 이름을 왜 반드시 grpc-otlp처럼 grpc-로 시작하게 지어야 하나요?
Istio는 쿠버네티스 Service의 포트 이름 접두사(http-, grpc-, tcp-)나 appProtocol을 보고 프로토콜을 판별합니다. Envoy의 OpenTelemetry 트레이서는 gRPC(HTTP/2) 로 Span을 전송하므로, 포트 이름이 grpc-로 시작하거나 appProtocol: grpc가 있어야 HTTP/2 스트림이 정상 개통됩니다.
🔗 다음 파트로 이동
드디어 M(VictoriaMetrics) · L(Loki) · T(Tempo) 세 저장소에 메트릭, 로그, 트레이스가 실시간으로 쏟아져 들어오고 있습니다!
다음 Part 11에서는 LGTM의 사령탑인 Grafana (11.5.2) 와 서비스 메시 관제탑인 Kiali 를 띄워 이 세 가지 데이터를 하나로 엮어내는 1클릭 상관분석(Correlation) 및 실시간 토폴로지 검증을 완성합니다!
네비게이션
| 이전 | 목록 | 다음 |
|---|---|---|
| ◀ Part 10 이론 | 목록으로 | Part 11 이론 ▶ |